列表中非常常見的搜尋列,
如下圖所示:
搜尋列的組成大約分幾種:
-src
|-app
|-cms
|-...
|-modules
|-search
|-search-input
|-search-input.component.css
|-search-input.component.html
|-search-input.component.ts
|-search-select
|-search-select.component.css
|-search-select.component.html
|-search-select.component.ts
|-search-date
|-search-date.component.css
|-search-date.component.html
|-search-date.component.ts
|-search.module.ts
|-search.ts
在上篇已經有敘述Search的基底,
接下來開始實作相對應的子組件。
search-input.component.ts
:@Component(...)
export class SearchInputComponent {
@Input() label: string; //搜尋項目名稱
@Input() selectLabel: string; //要搜尋的變數
@Input() textholder: string;
@Input() searchObj: Search; //頁面元素tab物件中的searchObj
@Output() searchSend: EventEmitter<Search> = new EventEmitter<Search>();
isNumber = false; //此搜尋項目本質是否為數字
searchValue: string;
constructor() {}
ngOnInit() {
this.init();
}
init() {
if (!!this.selectLabel) {
switch (this.selectLabel) {
case "id":
this.isNumber = true;
this.searchValue = this.searchObj.idSel;
break;
case "name":
this.searchValue = this.searchObj.name;
break;
}
}
}
setSearch() {
switch (this.selectLabel) {
case "id":
this.searchObj.idSel = this.searchValue;
break;
case "name":
this.searchObj.name = this.searchValue;
break;
}
}
resetSearchValue() {
this.searchValue = "";
}
validSearch(event) {
let obj = { type: this.selectLabel, valid: event.valid };
this.searchObj.setValidObjs(obj);
this.getSearch();
}
getSearch(reset = false) {
if (reset) {
this.resetSearchValue();
let obj = { type: this.selectLabel, valid: true };
this.searchObj.setValidObjs(obj);
}
this.setSearch();
this.searchSend.emit(this.searchObj);
}
}
--
search-input.component.html
:<div class="item-wrapper search">
<div>
<span>{{ label | translate }}</span>
</div>
<div *ngIf="!isNumber">
<input
type="text"
[(ngModel)]="searchValue"
[placeholder]="textholder | translate"
(change)="getSearch()"
/>
<mat-icon *ngIf="searchValue != ''" (click)="getSearch(true)"
class="del-search">
cancel
</mat-icon>
</div>
<div *ngIf="isNumber">
<input
type="text"
name="idSel"
#idSel="ngModel"
[(ngModel)]="searchValue"
[placeholder]="textholder | translate"
(change)="validSearch(idSel)"
[fnValidator]="'numberOnlyValidator'"
/>
<mat-icon *ngIf="searchValue != ''" (click)="getSearch(true)"
class="del-search">
cancel
</mat-icon>
<validation-messages [control]="idSel"></validation-messages>
</div>
</div>
實際運用時:
在會員列表中需要搜尋會員編號跟會員帳號:
<!--customer/list/list.component.html-->
<public-search-input
[label]="'customer_id'"
[textholder]="'import_customer_id'"
[selectLabel]="'id'"
[searchObj]="tab.searchObj"
></public-search-input>
<public-search-input
[label]="'customer_name'"
[textholder]="'import_customer_account'"
[selectLabel]="'name'"
[searchObj]="tab.searchObj"
></public-search-input>
search-select.component.ts
:@Component({
selector: "public-search-select",
templateUrl: "./search-select.component.html",
styleUrls: ["./search-select.component.css"]
})
export class SearchSelectComponent {
@Input() label: string; //搜尋項目名稱
@Input() selectLabel: string; //要搜尋的變數
@Input() searchObj: Search; //頁面元素tab物件中的searchObj
@Input() selects: { id: number; name: number }[]; //要顯示的options
@Output() searchSend: EventEmitter<Search> = new EventEmitter<Search>();
searchValue: string = "";
constructor() {}
ngOnInit() {
this.init();
}
init() {
if (!!this.selectLabel) {
switch (this.selectLabel) {
case "levelId":
this.searchValue = this.searchObj.levelIdSel;
break;
case "typeId":
this.searchValue = this.searchObj.typeIdSel;
break;
case "status":
this.searchValue = this.searchObj.statusSel;
break;
}
}
}
setSearch() {
switch (this.selectLabel) {
case "levelId":
this.searchObj.levelIdSel = this.searchValue;
break;
case "typeId":
this.searchObj.typeIdSel = this.searchValue;
break;
case "status":
this.searchObj.statusSel = this.searchValue;
break;
}
}
getSearch() {
this.setSearch();
this.searchSend.emit(this.searchObj);
}
}
--
search-select.component.html
:<div class="item-wrapper search">
<div>
<span>{{ label | translate }}</span>
</div>
<div *ngIf="!!selects && !!selects.length">
<select name="se_status" id="se_status"
[(ngModel)]="searchValue" (change)="getSearch()">
<option value="" selected>{{ "all" | translate }}</option>
<option *ngFor="let s of selects" [value]="s.id">
{{ s.name | translate }}
</option>
</select>
</div>
</div>
實際運用時:
在會員列表中需要搜尋會員等級:
<!--customer/list/list.component.html-->
<public-search-select
*ngIf="!!levels && !!levels.length"
[label]="'level_type'"
[selectLabel]="'levelId'"
[searchObj]="tab.searchObj"
[selects]="levels"
></public-search-select>
search-date.component.ts
:interface IDateData {
valid: boolean;
value: Moment;
}
@Component({...})
export class SearchDateComponent {
@Input() searchObj: Search; //頁面元素tab物件中的searchObj
@Input() toggleMb: boolean; //是否為手機尺寸,排版用
@Output() searchSend: EventEmitter<Search> = new EventEmitter<Search>();
startObj = <IDateData>{ valid: true, value: null };
endObj = <IDateData>{ valid: true, value: null };
constructor() {}
ngOnInit() {
this.init();
}
init() {
this.startObj.value = this.searchObj.start;
this.endObj.value = this.searchObj.end;
}
setDate(type: string, event) {
if (type === "start") {
this.startObj.valid = event.valid;
this.setSearch(type, this.startObj);
} else {
this.endObj.valid = event.valid;
this.setSearch(type, this.endObj);
}
this.getSearch();
}
setSearch(type: string, obj: IDateData) {
if (obj.valid) {
this.searchObj[type] = obj.value;
}
}
getSearch() {
this.searchObj.setValidObjs(
<IValid>{ type: "start", valid: this.startObj.valid }
);
this.searchObj.setValidObjs(
<IValid>{ type: "end", valid: this.endObj.valid }
);
this.searchSend.emit(this.searchObj);
}
}
--
search-date.component.html
:<div *ngIf="!toggleMb" class="flex">
<div class="item-wrapper search">
<div>
<span>{{ "search_date" | translate }}</span>
</div>
<div>
<input
[placeholder]="'import_date' | translate"
[(ngModel)]="startObj.value"
[owlDateTimeTrigger]="start"
[owlDateTime]="start"
#s="ngModel"
(dateTimeInput)="setDate('start', s)"
required
/>
<owl-date-time #start></owl-date-time>
<validation-messages [control]="s" class="tips">
</validation-messages>
</div>
</div>
<div class="item-wrapper search">
<div>
<span>~</span>
</div>
<div>
<input
[placeholder]="'import_date' | translate"
[(ngModel)]="endObj.value"
[owlDateTimeTrigger]="end"
[owlDateTime]="end"
#e="ngModel"
[min]="s.value"
(dateTimeInput)="setDate('end', e)"
required
/>
<owl-date-time #end></owl-date-time>
<validation-messages [control]="e" class="tips">
</validation-messages>
</div>
</div>
</div>
<ng-container *ngIf="toggleMb">
<div class="item-wrapper search">
<div>
<span>{{ "start" | translate }}</span>
</div>
<div>
<input
[placeholder]="'import_date' | translate"
[(ngModel)]="startObj.value"
[owlDateTimeTrigger]="start"
[owlDateTime]="start"
#s="ngModel"
(dateTimeInput)="setDate('start', s)"
required
/>
<owl-date-time [pickerMode]="'dialog'" #start></owl-date-time>
<validation-messages [control]="s"></validation-messages>
</div>
</div>
<div class="item-wrapper search">
<div>
<span>{{ "end" | translate }}</span>
</div>
<div>
<input
[placeholder]="'import_date' | translate"
[(ngModel)]="endObj.value"
[owlDateTimeTrigger]="end"
[owlDateTime]="end"
#e="ngModel"
[min]="s.value"
(dateTimeInput)="setDate('end', e)"
required
/>
<owl-date-time [pickerMode]="'dialog'" #end></owl-date-time>
<validation-messages [control]="e"></validation-messages>
</div>
</div>
</ng-container>
實際運用時:
在訂單列表中需要搜尋下單時間:
<public-search-date
[searchObj]="tab.searchObj"
[toggleMb]="isMobileToggle"
></public-search-date>
最後我們要設定 ListComponent.ts 中的Search設定,
所有ListComponent都是一樣的程序。
舉個例子:
list.component.ts
://order/list/list.component.ts
...
import { Search } from "../../../modules/search/search";
import * as _moment from "moment";
import { TabService } from 'src/app/modules/tab/tab.service';
const moment = (_moment as any).default ? (_moment as any).default : _moment;
@Component(...))
export class OrderListComponent implements OnInit {
tab: ITabBase;
...
constructor(...) {}
/*get resolve */
ngOnInit() {...}
/*初始 */
init() {
if (!this.tab.pageObj) {
this.tab.pageObj = <IPage>{
pageIndex: 0,
pageSize: PAGESIZE,
length: 0
};
}
if (!this.tab.searchObj) {
this.tab.searchObj = new Search();
this.tab.searchObj.setSearchDate("month");
this.tab.searchObj.expand = "customer";
} else {
this.tab.searchObj = Object.assign(
new Search(),
this.tab.searchObj
);
this.tab.searchObj.start = new moment(this.tab.searchObj.start);
this.tab.searchObj.end = new moment(this.tab.searchObj.end);
}
this.setDatas(true);
}
/*換分頁 */
onSetPage(pageObj: IPage) {
...
}
/*搜尋 */
onSearch() {
this.tab.pageObj.pageIndex = 0;
this.setDatas();
}
/*裝畫面資料 */
setDatas(isDataInit = false) {
this.isLoadingToggle = true;
let url = this.dataService.setUrl("orders", this.setFilters());
this.dataService.getData(url, this.tab.pageObj)
.subscribe((data: IData) => {
...
});
}
/*儲存storage */
setLoadingDatas(isDataInit = false) {
...
}
/*裝Search條件 */
setFilters(): IFilter[] {
let f: IFilter[] = [];
let s = this.tab.searchObj.getSearch();
let keys = Object.keys(s);
keys.forEach((item: string) => {
f.push({
key: item,
val: s[item]
});
});
return f;
}
...
}
--
list.component.html
:<div class="container">
<div class="search-box">
<div class="flex" *ngIf="!!tab.searchObj">
<public-search-select
[label]="'status'"
[selectLabel]="'status'"
[searchObj]="tab.searchObj"
[selects]="ORDERSTATUS"
></public-search-select>
<public-search-date
[searchObj]="tab.searchObj"
[toggleMb]="isMobileToggle"
></public-search-date>
<div class="flex">
<button
(click)="onSearch()"
class="button search-btn pb radius-20"
[disabled]="!tab.searchObj.check"
[ngClass]="{ disable: !tab.searchObj.check }"
>
<mat-icon svgIcon="search"></mat-icon>
</button>
</div>
<div class="search-action">
<button class="button small-icons radius-5 pr-10 pink"
(click)="openDialog('insert')">
<mat-icon>add</mat-icon>
<span>{{ "insert" | translate }}</span>
</button>
</div>
</div>
</div>
...
</div>
此為完整專案範例碼,連線方式為json-server。
https://stackblitz.com/edit/ngcms-json-server
一開始會跳出提示視窗顯示fail為正常,
請先從範例專案裡下載或是複製db.json
到本地端,
並下指令:
json-server db.json
json-server開啟成功後請連結此網址:
https://ngcms-json-server.stackblitz.io/cms?token=bc6e113d26ce620066237d5e43f14690